import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

/**
 * <p>
 * Materialien zu den zentralen NRW-Abiturpruefungen im Fach Informatik ab 2018
 * </p>
 * <p>
 * Klasse Client
 * </p>
 * <p>
 * Objekte von Unterklassen der abstrakten Klasse Client ermoeglichen
 * Netzwerkverbindungen zu einem Server mittels TCP/IP-Protokoll. Nach
 * Verbindungsaufbau koennen Zeichenketten (Strings) zum Server gesendet und von
 * diesem empfangen werden, wobei der Nachrichtenempfang nebenlaeufig geschieht. 
 * Zur Vereinfachung finden Nachrichtenversand und -empfang zeilenweise statt,
 * d. h., beim Senden einer Zeichenkette wird ein Zeilentrenner ergaenzt und beim
 * Empfang wird dieser entfernt. Jede empfangene Nachricht wird einer
 * Ereignisbehandlungsmethode uebergeben, die in Unterklassen implementiert werden
 * muss. Es findet nur eine rudimentaere Fehlerbehandlung statt, so dass z.B.
 * Verbindungsabbrueche nicht zu einem Programmabbruch fuehren. Eine einmal
 * unterbrochene oder getrennte Verbindung kann nicht reaktiviert werden.
 * </p>
 * 
 * @author Qualitaets- und UnterstuetzungsAgentur - Landesinstitut fuer Schule
 * @version 30.08.2016
 */

public abstract class Client{
  private MessageHandler messageHandler;

  private class MessageHandler extends Thread{
    private SocketWrapper socketWrapper;
    private boolean active;

    private class SocketWrapper{
      private Socket socket;
      private BufferedReader fromServer;
      private PrintWriter toServer;

      public SocketWrapper(String pServerIP, int pServerPort){
        try{
          socket = new Socket(pServerIP, pServerPort);
          toServer = new PrintWriter(socket.getOutputStream(), true);
          fromServer = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        } catch (IOException e){
          socket = null;
          toServer = null;
          fromServer = null;
        }
      }

      public String receive(){
        if(fromServer != null)
          try{
            return fromServer.readLine();
          } catch (IOException e){}
        return(null);
      }

      public void send(String pMessage){
        if(toServer != null){
          toServer.println(pMessage);
        }
      }

      public void close(){
        if(socket != null)
          try{
            socket.close();
          } catch (IOException e) {
            /*
             * Falls eine Verbindung getrennt werden soll, deren Endpunkt
             * nicht mehr existiert bzw. ihrerseits bereits beendet worden ist,
             * geschieht nichts.
             */
          }
      }
    }

    private MessageHandler(String pServerIP, int pServerPort) {
      socketWrapper = new SocketWrapper(pServerIP, pServerPort);
      start();
      if(socketWrapper.socket != null)
        active = true;
    }

    public void run(){
      String message = null;
      while (active){
        message = socketWrapper.receive();
        if (message != null)
          processMessage(message);
        else
          close();
      }
    }

    private void send(String pMessage){
      if(active)
        socketWrapper.send(pMessage);
    }

    private void close(){
      if(active){
        active = false;
        socketWrapper.close();
      }
    }
  }

  public Client(String pServerIP, int pServerPort){
    messageHandler = new MessageHandler(pServerIP, pServerPort);
  }

  public boolean isConnected(){
    return(messageHandler.active);
  }

  public void send(String pMessage){
    messageHandler.send(pMessage);
  }

  public void close() {
    messageHandler.close();
  }

  public abstract void processMessage(String pMessage);

}

